﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Windows.Forms;
using FxSuperCore.Log;
using FxSuperCore.Utils;
using FxWinSuper.Plugin.Data;
using FxWinSuper.Plugin.Extension;
using FxWinSuper.UI.Menu.Controls;
using FxWinSuper.Util;
using HandyControl.Controls;
using HandyControl.Data;
using Newtonsoft.Json;
using OBS;
using OBS.Model;
using WinSupperUpdateService.Model;
using WinSupperUpdateService.Service;
using WinSupperUpdateService.UI;
using Timer = System.Threading.Timer;

namespace WinSupperUpdateService.Store
{
	internal class MinioPluginStore : IPluginStore
	{
		public static MinioPluginStore Instance { get; }
		private static ILogPrint Logger = PluginHelper.GetCurrentLogPrint();
		private readonly ObsClient obsClient;
		private static readonly string BucketName = "fxwinsupper";
		// 更新软件包操作包操作的临时根目录
		private static readonly string SofwareUpateRootPackagePath = SoftwareUpdate.SofwareUpateRootPackagePath;
		// 下载后更新包文件路径
		private static readonly string TmpSofwareUpatePackageZipPath = SoftwareUpdate.TmpSofwareUpatePackageZipPath;
		// 更新包解压后的文件夹目录
		private static readonly string SofwareUpatePackagDecompressionPath = SoftwareUpdate.SofwareUpatePackagDecompressionPath;
		// 旧软件包备份路径
		private static readonly string SofwareUpatePackagBackupPath = SoftwareUpdate.SofwareUpatePackagBackupPath;
		// 检测更新定时器
		private static readonly int MainSoftwareDetectPeriod = 30 * 60 * 1000;
		private readonly Timer MainSoftwareDetectTimer;
		// 是否已经提示过要进行软件更新
		private volatile bool IsTipedUpdated = false;
		// 是否已经提示过软件更新出错
		private volatile bool IsTipUpdateDetectError = false;

		static MinioPluginStore()
		{
			Instance = new MinioPluginStore();
		}

		private MinioPluginStore()
		{
			ObsConfig obsConfig = new ObsConfig();
			obsConfig.Endpoint = "http://127.0.0.1:9000";
			obsClient = new ObsClient("minioadmin", "minioadmin", obsConfig);
			MainSoftwareDetectTimer = new Timer(DetectMainSoftwareVersionTimerFunc, false, 0, MainSoftwareDetectPeriod);
		}

		/// <summary>
		/// 检测主软件版本是否有更新
		/// </summary>
		public void DetectMainSoftwareVersion(bool tipUser)
		{
			IsTipedUpdated = false;
			Task.Run(() => DetectMainSoftwareVersionTimerFunc(tipUser));
		}

		/// <summary>
		/// 检测主软件版本
		/// </summary>
		private async void DetectMainSoftwareVersionTimerFunc(object state)
		{
			bool tipUser = (bool) state;
			Logger.Debug("开始检测软件版本");
			try
			{
				PluginMeta pluginMeta = await GetPluginMeta();
				if (pluginMeta.SoftwareVersionCode <= ConfigUtil.VersionCode)
				{
					Logger.Debug("软件已更新到最新版本");
					if (tipUser)
					{
						CommonUtils.ShowWinToast("提示", "软件已经是最新版本", ToolTipIcon.Info, 5000);
					}
					return;
				}
				Logger.Debug($"有新版本[{pluginMeta.SoftwareVersion}]可用");
				if (IsTipedUpdated)
				{
					return;
				}
				IsTipedUpdated = true;
				// 提示用户是否进行更新
				PluginHelper.RunOnUiThread(() =>
				{
					UpdateNotify updateNotify = new UpdateNotify()
					{
						Title = $"FxWinSupper V{pluginMeta.SoftwareVersion}可更新",
						Msg = pluginMeta.SoftwareUpdateMsg
					};
					Notification notification = Notification.Show(updateNotify, ShowAnimation.Fade, true);
					updateNotify.OnCancel += () =>
					{
						notification.Close();
					};
					updateNotify.OnConfirm += () =>
					{
						notification.Close();
						ExecUpdateProcess(pluginMeta);
					};
					ApplicationUtil.BeforeClose += (sender, args) => notification?.Close();
				});
			}
			catch (Exception e)
			{
				IsTipedUpdated = false;
				Logger.Error($"[Software Update] {e.Message} {e.StackTrace}");
				if (!IsTipUpdateDetectError || tipUser)
				{
					CommonUtils.ShowWinToast("提示", "软件更新检测失败");
					IsTipUpdateDetectError = true;
				}
			}
		}

		private async void ExecUpdateProcess(PluginMeta pluginMeta)
		{
			ApplicationUtil.HideMainWindow();
			DownloadView downloadView = new DownloadView()
			{
				Title = "正在下载更新包",
				Width = 400,
				Value = 0,
				NeedConfirm = false
			};
			Notification notification = Notification.Show(downloadView, ShowAnimation.Fade, true);
			try
			{
				// 清空临时文件,创建目录结构
				downloadView.Title = "正在进行更新前准备...";
				Logger.Info($"[Software Update] use tmp folder: {SofwareUpateRootPackagePath}");
				if (Directory.Exists(SofwareUpateRootPackagePath))
				{
					Directory.Delete(SofwareUpateRootPackagePath, true);
				}

				Directory.CreateDirectory(SofwareUpateRootPackagePath);
				Directory.CreateDirectory(SofwareUpatePackagDecompressionPath);
				Directory.CreateDirectory(SofwareUpatePackagBackupPath);
				await Task.Delay(500);
				// 下载软件包
				downloadView.Title = "正在下载更新包...";
				Logger.Info($"[Software Update] download update package: {pluginMeta.SoftwareUpdatePackagePath}[{pluginMeta.SoftwareVersion}]");
				GetObjectRequest request = new GetObjectRequest()
				{
					BucketName = BucketName,
					ObjectKey = pluginMeta.SoftwareUpdatePackagePath,
					ProgressInterval = 4096,
					ProgressType = ProgressTypeEnum.ByBytes
				};
				request.DownloadProgress += (sender, status) =>
				{
					downloadView.Value = status.TransferPercentage;
				};
				IAsyncResult asyncResult = obsClient.BeginGetObject(request, null, null);
				GetObjectResponse response = await Task.Factory.FromAsync(asyncResult, obsClient.EndGetObject);

				if ((int)response.StatusCode >= 300)
				{
					throw new Exception($"下载软件包失败, HttpCode={response.StatusCode}");
				}

				response.WriteResponseStreamToFile(TmpSofwareUpatePackageZipPath);
				Logger.Info($"[Software Update] download package success to [{TmpSofwareUpatePackageZipPath}]");
				// 校验md5
				downloadView.Title = "正在校验更新包...";
				if (CalcFileMd5(TmpSofwareUpatePackageZipPath) != pluginMeta.SoftwareUpdatePackageMd5)
				{
					throw new Exception("下载的软件更新包已损坏");
				}

				await Task.Delay(200);
				// 解压压缩包
				downloadView.Title = "正在解压更新包...";
				Logger.Info($"[Software Update] Decompression package to [{SofwareUpatePackagDecompressionPath}]");
				using (ZipArchive zipArchive = ZipFile.OpenRead(TmpSofwareUpatePackageZipPath))
				{
					zipArchive.ExtractToDirectory(SofwareUpatePackagDecompressionPath, true);
				}

				await Task.Delay(500);
				// 调用更新进程进行 备份原软件包，更新新软件包
				downloadView.Title = "即将开始更新...";
				await Task.Delay(1000);
				Logger.Info($"[Software Update] invoke update process exec update");
				SoftwareUpdate.ExecUpdate($"\"{Environment.CurrentDirectory}\"");
				notification.Close();
				ApplicationUtil.Exit();
			}
			catch (Exception e)
			{
				if (e is ObsException obsException)
				{
					Logger.Error($"[Software Update] 下载软件更新包失败, errorCode:[{obsException.ErrorCode}], msg: [{obsException.ErrorMessage}]");
				}
				else
				{
					Logger.Error($"[Software Update] {e.Message} {e.StackTrace}");
				}

				IsTipedUpdated = false;
				notification.Close();
				ApplicationUtil.ShowMainWindow();
				ApplicationUtil.SwitchPage("日志");
				CommonUtils.ShowWinToast("提示", "更新软件发生错误", System.Windows.Forms.ToolTipIcon.Error);
			}
		}


		private async Task<PluginMeta> GetPluginMeta()
		{
			GetObjectRequest request = new GetObjectRequest()
			{
				BucketName = BucketName,
				ObjectKey = "meta.json",
			};
			try
			{
				IAsyncResult asyncResult = obsClient.BeginGetObject(request, null, null);
				GetObjectResponse response = await Task.Factory.FromAsync(asyncResult, obsClient.EndGetObject);
				if ((int)response.StatusCode >= 300)
				{
					throw new Exception($"获取插件信息失败, HttpCode={response.StatusCode}");
				}

				using (StreamReader sr = new StreamReader(response.OutputStream))
				{
					string json = sr.ReadToEnd();
					return JsonConvert.DeserializeObject<PluginMeta>(json);
				}
			}
			catch (ObsException e)
			{
				throw new Exception($"获取插件Meta信息失败, errorCode:[{e.ErrorCode}], msg: [{e.ErrorMessage}]");
			}
		}

		public async Task<List<RemotePluginEntity>> GetAllPlugins()
		{
			PluginMeta pluginMeta = await GetPluginMeta();
			return new List<RemotePluginEntity>(pluginMeta.Plugins);
		}

		public async Task<List<RemotePluginEntity>> SearchPlugin(string pluginName)
		{
			List<RemotePluginEntity> remotePluginEntities = new List<RemotePluginEntity>();
			List<RemotePluginEntity> allPluginEntities = await GetAllPlugins();
			allPluginEntities.ForEach(plugin =>
			{
				if (plugin.PluginName.ToLower().Contains(pluginName.ToLower()))
				{
					remotePluginEntities.Add(plugin);
				}
			});
			return remotePluginEntities;
		}

		public async Task<RemotePluginEntity> GetPluginInfo(string pluginName)
		{
			List<RemotePluginEntity> allPluginEntities = await GetAllPlugins();
			return allPluginEntities.FirstOrDefault(plugin => plugin.PluginName == pluginName);
		}

		public async Task<bool> DownloadPlugin(string pluginName, string targetFilepath, Action<int> ProgressCallback = null)
		{
			RemotePluginEntity remotePluginEntity = await GetPluginInfo(pluginName);
			if (remotePluginEntity == null)
			{
				throw new Exception($"未在插件商店中获取到插件[{pluginName}]相关信息");
			}

			PluginEntity pluginEntity = remotePluginEntity as PluginEntity;

			if (File.Exists(targetFilepath))
			{
				File.Delete(targetFilepath);
			}

			GetObjectRequest request = new GetObjectRequest()
			{
				BucketName = BucketName,
				ObjectKey = pluginEntity.PackagePath,
				ProgressType = ProgressTypeEnum.ByBytes,
				ProgressInterval = 4096
			};
			request.DownloadProgress += (sender, status) =>
			{
				ProgressCallback?.Invoke(status.TransferPercentage);
			};
			try
			{
				IAsyncResult asyncResult = obsClient.BeginGetObject(request, null, null);
				GetObjectResponse response = await Task.Factory.FromAsync(asyncResult, obsClient.EndGetObject);

				if ((int)response.StatusCode >= 300)
				{
					throw new Exception($"获取插件信息失败, HttpCode={response.StatusCode}");
				}
				response.WriteResponseStreamToFile(targetFilepath);
			}
			catch (ObsException e)
			{
				throw new Exception($"下载插件出错, errorCode:[{e.ErrorCode}], msg: [{e.ErrorMessage}]");
			}

			if (CalcFileMd5(targetFilepath) != pluginEntity.PackageMd5)
			{
				File.Delete(targetFilepath);
				throw new Exception("下载的插件包损坏，请重新下载");
			}

			return true;
		}

		private string CalcFileMd5(string filepath)
		{
			if (!File.Exists(filepath))
			{
				return null;
			}
			using (var md5 = MD5.Create())
			{
				using (var stream = File.OpenRead(filepath))
				{
					var hash = md5.ComputeHash(stream);
					return BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
				}
			}
		}
	}
}
